//
//  DPObjCRuntime.m
//  HigherOrderMessaging
//
//  Created by Ofri Wolfus on 03/11/06.
//  Copyright 2006 Ofri Wolfus. All rights reserved.
//

#import "DPObjCRuntime.h"
//#include <objc/objc-class.h>

#if defined(DP_EXTERN_INLINE)
#undef DP_EXTERN_INLINE
#define DP_EXTERN_INLINE /**/
#endif

#pragma mark Versions Compatiblity

#if !__OBJC2__

char *method_copyReturnType(Method m) {
	char	*type = method_getTypeEncoding(m);
	int		i, l = strlen(type);
	char	*r;
	
	//Skip type qualifiers
	while (*type == _C_CONST
		   || *type == _C_IN
		   || *type == _C_INOUT
		   || *type == _C_OUT
		   || *type == _C_BYCOPY
		   || *type == _C_BYREF
		   || *type == _C_ONEWAY)
    {
		type += 1;
    }
	
	//Find the offset of the return type. When we reach it, we found what we looked for.
	for (i = 0; i < l; i++) {
		switch (type[i]) {
			case '0' ... '9':
				r = malloc(sizeof(char) * (i == 1 ? 2 : i));
				strncpy(r, type, i);
				r[i] = '\0';
				return r;
				
			default:
				break;
		}
	}
	
	return NULL;
}

char *method_copyArgumentType(Method m, unsigned int index) {
	char *s, *r;
	size_t l;
	int i;
	
	method_getArgumentInfo(m, index, (const char **)&s, NULL);
	l = strlen(s);
	
	for (i = 0; i < l; i++) {
		switch (s[i]) {
			case '0' ... '9':
				r = malloc(sizeof(char) * (i == 1 ? 2 : i));
				strncpy(r, s, i);
				r[i] = '\0';
				return r;
				
			default:
				break;
		}
	}
	
	return NULL;
}

void method_getReturnType(Method m, char *dst, size_t dst_len) {
	size_t l, len = strlen(m->method_types);
	
	for (l = 0; l < len; l++) {
		switch (m->method_types[l]) {
			case '0' ... '9':
				break;
			default:
				continue;
		}
	}
	
	strncpy(dst, m->method_types, MIN(l, dst_len));
}

void method_getArgumentType(Method m, unsigned int index,
							char *dst, size_t dst_len)
{
	char *s;
	size_t l, len;
	
	method_getArgumentInfo(m, index, (const char **)&s, NULL);
	len = strlen(s);
	
	for (l = 0; l < len; l++) {
		switch (s[l]) {
			case '0' ... '9':
				break;
			default:
				continue;
		}
	}
	
	strncpy(dst, s, MIN(l, dst_len));
}

BOOL class_respondsToSelector(Class cls, SEL sel) {
	return (class_getClassMethod(cls, sel) ?: class_getInstanceMethod(cls, sel)) != NULL;
}

Method * class_copyMethodList(Class cls, unsigned int *outCount) {
	void *iterator = 0;
	struct objc_method_list *mlist;
	Method *methods;
	Method *m;
	
	// Count all our methods
	*outCount = 0U;
	while ((mlist = class_nextMethodList(cls, &iterator)))
		(*outCount) += mlist->method_count;
	
	// Allocate a block for all methods
	methods = calloc(*outCount, sizeof(Method));
	// After each iteration m points to where the next method should be
	m = methods;
	// We iterate from the begining again
	iterator = 0;
	
	while ((mlist = class_nextMethodList(cls, &iterator))) {
		int i;
		
		for (i = 0; i < mlist->method_count; i++) {
			*m = &(mlist->method_list[i]);
			++m;
		}
	}
	
	return methods;
}

BOOL class_addMethod(Class cls, SEL name, IMP imp, 
					 const char *types)
{
	Method m = malloc(sizeof(struct objc_method));
	struct objc_method_list *list = malloc(sizeof(struct objc_method_list));
	
	m->method_name = name;
	m->method_imp = imp;
	m->method_types = (char *)types;
	
	list->method_count = 1;
	list->method_list[0] = *m;
	
	class_addMethods(cls, list);
	return YES;
}

#endif // !__OBJC2__

#pragma mark -
#pragma mark Additional API

/***********************************************************************
* _dp_subtypeUntil.
*
* Delegation.
* Code taken from Apple's Objective-C runtime, released under ASPL.
**********************************************************************/
static int	_dp_subtypeUntil(const char *type, char end)
{
    int		level = 0;
    const char *	head = type;
	
    //
    while (*type) {
        if (!*type || (!level && (*type == end)))
            return (int)(type - head);
		
        switch (*type) {
            case ']': case '}': case ')': level--; break;
            case '[': case '{': case '(': level += 1; break;
        }
		
        type += 1;
    }
    return 0;
}

/***********************************************************************
* _dp_skipFirstType.
* Code taken from Apple's Objective-C runtime, released under ASPL.
**********************************************************************/
static const char *	_dp_skipFirstType(const char *	type)
{
    while (1) {
        switch (*type++) {
            case 'O':	/* bycopy */
            case 'n':	/* in */
            case 'o':	/* out */
            case 'N':	/* inout */
            case 'r':	/* const */
            case 'V':	/* oneway */
            case '^':	/* pointers */
                break;
				
                /* arrays */
            case '[':
                while ((*type >= '0') && (*type <= '9'))
                    type += 1;
                return type + _dp_subtypeUntil (type, ']') + 1;
				
                /* structures */
            case '{':
                return type + _dp_subtypeUntil (type, '}') + 1;
				
                /* unions */
            case '(':
                return type + _dp_subtypeUntil (type, ')') + 1;
				
                /* basic types */
            default:
                return type;
        }
    }
}


//#pragma mark -
#pragma mark Objective-C II
#if __OBJC2__

unsigned dp_getArgumentInfo(const char *typedesc, int arg, const  char** type, int*  offset) {
    unsigned	nargs		   = 0;
    unsigned	self_offset	   = 0;
    BOOL		offset_is_negative = NO;
	
    // First, skip the return type
    typedesc = _dp_skipFirstType(typedesc);
	
    // Next, skip stack size
    while ((*typedesc >= '0') && (*typedesc <= '9'))
        typedesc += 1;
	
    // Now, we have the arguments - position typedesc to the appropriate argument
    while (*typedesc && nargs != arg)
    {
		
        // Skip argument type
        typedesc = _dp_skipFirstType(typedesc);
		
        if (nargs == 0)
        {
            // Skip GNU runtime's register parameter hint
            if (*typedesc == '+') typedesc++;
			
            // Skip negative sign in offset
            if (*typedesc == '-')
            {
                offset_is_negative = YES;
                typedesc += 1;
            }
            else
                offset_is_negative = NO;
			
            while ((*typedesc >= '0') && (*typedesc <= '9'))
                self_offset = self_offset * 10 + (*typedesc++ - '0');
            if (offset_is_negative)
                self_offset = -(self_offset);
			
        }
		
        else
        {
            // Skip GNU runtime's register parameter hint
            if (*typedesc == '+') typedesc++;
			
            // Skip (possibly negative) argument offset
            if (*typedesc == '-')
                typedesc += 1;
            while ((*typedesc >= '0') && (*typedesc <= '9'))
                typedesc += 1;
        }
		
        nargs += 1;
    }
	
    if (*typedesc)
    {
        unsigned arg_offset = 0;
		
        *type	 = typedesc;
        typedesc = _dp_skipFirstType(typedesc);
		
        if (arg == 0)
        {
#ifdef hppa
            *offset = -sizeof(id);
#else
            *offset = 0;
#endif // hppa
        }
		
        else
        {
            // Skip GNU register parameter hint
            if (*typedesc == '+') typedesc++;
			
            // Pick up (possibly negative) argument offset
            if (*typedesc == '-')
            {
                offset_is_negative = YES;
                typedesc += 1;
            }
            else
                offset_is_negative = NO;
			
            while ((*typedesc >= '0') && (*typedesc <= '9'))
                arg_offset = arg_offset * 10 + (*typedesc++ - '0');
            if (offset_is_negative)
                arg_offset = - arg_offset;
			
#ifdef hppa
            // For stacks which grow up, since margs points
            // to the top of the stack or the END of the args,
            // the first offset is at -sizeof(id) rather than 0.
            self_offset += sizeof(id);
#endif
            *offset = arg_offset - self_offset;
        }
		
    }
	
    else
    {
        *type	= 0;
        *offset	= 0;
    }
	
    return nargs;
}

/*
 * The following code was taken from Apple's runtime sources
 * and is covered by the ASPL license.
 */
unsigned dp_getNumberOfArguments(const char *typedesc) {
	unsigned		nargs;
	
    // First, skip the return type
    typedesc = _dp_skipFirstType(typedesc);
	
    // Next, skip stack size
    while ((*typedesc >= '0') && (*typedesc <= '9'))
        typedesc += 1;
	
    // Now, we have the arguments - count how many
    nargs = 0;
    while (*typedesc) {
        // Traverse argument type
        typedesc = _dp_skipFirstType(typedesc);
		
        // Skip GNU runtime's register parameter hint
        if (*typedesc == '+') typedesc++;
		
        // Traverse (possibly negative) argument offset
        if (*typedesc == '-')
            typedesc += 1;
        while ((*typedesc >= '0') && (*typedesc <= '9'))
            typedesc += 1;
		
        // Made it past an argument
        nargs += 1;
    }
	
    return nargs;
}


#ifndef __alpha__
unsigned	dp_getSizeOfArguments(const char *typedesc)
{
    unsigned		stack_size;
#if defined(__ppc__) || defined(ppc)
    unsigned		trueBaseOffset;
    unsigned		foundBaseOffset;
#endif
	
    // Get our starting points
    stack_size = 0;
	
    // Skip the return type
#if defined (__ppc__) || defined(ppc)
    // Struct returns cause the parameters to be bumped
    // by a register, so the offset to the receiver is
    // 4 instead of the normal 0.
    trueBaseOffset = (*typedesc == '{') ? 4 : 0;
#endif
    typedesc = _dp_skipFirstType(typedesc);
	
    // Convert ASCII number string to integer
    while ((*typedesc >= '0') && (*typedesc <= '9'))
        stack_size = (stack_size * 10) + (*typedesc++ - '0');
#if defined (__ppc__) || defined(ppc)
    // NOTE: This is a temporary measure pending a compiler fix.
    // Work around PowerPC compiler bug wherein the method argument
    // string contains an incorrect value for the "stack size."
    // Generally, the size is reported 4 bytes too small, so we apply
    // that fudge factor.  Unfortunately, there is at least one case
    // where the error is something other than -4: when the last
    // parameter is a double, the reported stack is much too high
    // (about 32 bytes).  We do not attempt to detect that case.
    // The result of returning a too-high value is that objc_msgSendv
    // can bus error if the destination of the marg_list copying
    // butts up against excluded memory.
    // This fix disables itself when it sees a correctly built
    // type string (i.e. the offset for the Id is correct).  This
    // keeps us out of lockstep with the compiler.
	
    // skip the '@' marking the Id field
    typedesc = _dp_skipFirstType(typedesc);
	
    // Skip GNU runtime's register parameter hint
    if (*typedesc == '+') typedesc++;
	
    // pick up the offset for the Id field
    foundBaseOffset = 0;
    while ((*typedesc >= '0') && (*typedesc <= '9'))
        foundBaseOffset = (foundBaseOffset * 10) + (*typedesc++ - '0');
	
    // add fudge factor iff the Id field offset was wrong
    if (foundBaseOffset != trueBaseOffset)
        stack_size += 4;
#endif
	
    return stack_size;
}

#else // __alpha__
unsigned dp_getSizeOfArguments(const char *type) {
	const char *	type;
    int		size;
    int		index;
    int		align;
    int		offset;
    unsigned	stack_size;
    int		nargs;
	
    nargs		= dp_getNumberOfArguments(type);
    stack_size	= (*method->method_types == '{') ? sizeof(void *) : 0;
	
    for (index = 0; index < nargs; index += 1)
    {
        (void) dp_getArgumentInfo(type, index, &type, &offset);
        size = sizeOfType(type, &align);
        stack_size += ((size + 7) & ~7);
    }
	
    return stack_size;
}
#endif // __alpha

// ===================================================================================== //
// ===================================================================================== //
// ===================================================================================== //

//#pragma mark -
#pragma mark Objective-C I
#else	// !__OBJC2__

unsigned dp_getNumberOfArguments(const char *typedesc) {
	struct objc_method m;
	m.method_types = (char *)typedesc;
	return method_getNumberOfArguments(&m);
}

unsigned dp_getArgumentInfo(const char *types, int argIndex, const  char** type, int*  offset) {
	struct objc_method m;
	m.method_types = (char *)types;
	return method_getArgumentInfo(&m, argIndex, type, offset);
}

unsigned dp_getSizeOfArguments(const char *types) {
	struct objc_method m;
	m.method_types = (char *)types;
	return method_getSizeOfArguments(&m);
}

#endif	// __OBJC2__

// ===================================================================================== //
// ===================================================================================== //
// ===================================================================================== //

#pragma mark -
#pragma mark Runtime Additions

BOOL class_isSubclassOfClass(Class cls, Class superCls) {
	while (cls) {
		if (cls == superCls)
			return YES;
		
		cls = class_getSuperclass(cls);
	}
	
	return NO;
}

BOOL object_respondsToSelector(id object, SEL sel) {
	return object_isInstance(object) ? class_getInstanceMethod(object, sel) != NULL
	: class_respondsToSelector(object, sel);
}

Class *dp_copyClassList(void) {
	int size = objc_getClassList(NULL, 0);
	Class *buff = calloc(size+1, sizeof(Class));
	objc_getClassList(buff, size);
	return buff;
}

unsigned int sel_getNumberOfArguments(SEL sel) {
	unsigned int c = 2U;
	const char *name = sel_getName(sel);
	
	for (name = sel_getName(sel); *name; ++name)
		if (*name == ':')
			++c;
	
	return c;
}


// ===================================================================================== //
// ===================================================================================== //
// ===================================================================================== //

#pragma mark -
#pragma mark Other

Method dp_getMethod(id obj, SEL sel) {
	return object_isInstance(obj) ? class_getInstanceMethod(object_getClass(obj), sel)
				: class_getClassMethod(obj, sel) ?: class_getInstanceMethod(obj, sel);
}

id dp_msgSendv(id target, SEL sel, marg_list args) {
	Method m = dp_getMethod(target, sel);
	unsigned args_size;
	
	// If our target can't respond to this message it'll forward it
	if (__builtin_expect(!m, 0))
		return [target forward:sel :args];
	
	args_size = method_getSizeOfArguments(m);
	
	switch (*method_getTypeEncoding(m)) {
		case _C_STRUCT_B:
			objc_msgSendv_stret(marg_getValue(args, 0, void *), target, sel, args_size, args);
			
#ifdef __i386__
		case _C_FLT:
		case _C_DBL:
			//case *@encode(long double):
			objc_msgSendv_fpret(target, sel, args_size, args);
#endif
			
#ifdef __x86_64__
		case @encode(long double)[0]:
			objc_msgSendv_fpret(target, sel, args_size, args);
#endif
			
		default:
			return objc_msgSendv(target, sel, args_size, args);
	}
	
	return nil;
}
